package g

import (
	"Alpaca/conf"
	"Alpaca/log"
	"container/list"
	"runtime"
	"sync"
)

type Go struct {
	CbChan chan func()
	CbNum  int
}

func New(l int) *Go {
	g := new(Go)
	g.CbChan = make(chan func(), l)
	return g
}

func (g *Go) Go(f func(), cb func()) {
	defer func() {
		g.CbNum++
		g.CbChan <- cb
		if r := recover(); r != nil {
			if conf.StackBufLen > 0 {
				buf := make([]byte, conf.StackBufLen)
				len := runtime.Stack(buf, false)
				log.Error("%v: %s", r, buf[:len])
			} else {
				log.Error("%v", r)
			}
		}
	}()

	f()
}

func (g *Go) Cb(cb func()) {
	defer func() {
		g.CbNum--
		if r := recover(); r != nil {
			if conf.StackBufLen > 0 {
				buf := make([]byte, conf.StackBufLen)
				len := runtime.Stack(buf, false)
				log.Error("%v: %s", r, buf[:len])
			} else {
				log.Error("%v", r)
			}
		}

		if cb != nil {
			cb()
		}
	}()
}

func (g *Go) Close() {
	for g.CbNum > 0 {
		g.Cb(<-g.CbChan)
	}
}

func (g *Go) Empty() bool {
	return g.CbNum == 0
}

type LinearContext struct {
	g              *Go
	linearGo       *list.List
	mutexLinearGo  sync.Mutex
	mutexExecution sync.Mutex
}

type LinearGo struct {
	f  func()
	cb func()
}

func (g *Go) NewLinearContext() *LinearContext {
	c := new(LinearContext)
	c.g = g
	c.linearGo = list.New()
	return c
}

func (c *LinearContext) Go(f func(), cb func()) {
	c.g.CbNum++

	c.mutexLinearGo.Lock()
	c.linearGo.PushBack(&LinearGo{f: f, cb: cb})
	c.mutexLinearGo.Unlock()

	go func() {
		c.mutexExecution.Lock()
		defer c.mutexExecution.Unlock()

		c.mutexLinearGo.Lock()
		e := c.linearGo.Remove(c.linearGo.Front()).(*LinearGo)
		c.mutexLinearGo.Unlock()

		defer func() {
			c.g.CbChan <- cb
			if r := recover(); r != nil {
				if conf.StackBufLen > 0 {
					buf := make([]byte, conf.StackBufLen)
					l := runtime.Stack(buf, false)
					log.Error("%v: %s", r, buf[:l])
				} else {
					log.Error("%v", r)
				}
			}
		}()

		e.f()
	}()
}
